package com.gitee.wsl.doc.prosemirror.transform.step

import com.gitee.wsl.doc.prosemirror.model.Schema
import com.gitee.wsl.doc.prosemirror.model.fragment.Slice
import com.gitee.wsl.doc.prosemirror.model.node.Node
import com.gitee.wsl.doc.prosemirror.model.node.ext.contentBetween
import com.gitee.wsl.doc.prosemirror.transform.map.Mappable
import com.gitee.wsl.doc.prosemirror.transform.map.StepMap
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.booleanOrNull
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put

// Replace a part of the document with a slice of content, but preserve a range of the replaced
// content by moving it into the slice.
class ReplaceAroundStep(
    // Create a replace-around step with the given range and gap. `insert` should be the point in
    // the slice into which the content of the gap should be moved. `structure` has the same meaning
    // as it has in the [`ReplaceStep`](#transform.ReplaceStep) class.

    // The start position of the replaced range.
    override val from: Int,
    // The end position of the replaced range.
    override val to: Int,
    // The start of preserved range.
    val gapFrom: Int,
    // The end of preserved range.
    val gapTo: Int,
    // The slice to insert.
    val slice: Slice,
    // The position in the slice where the preserved range should be inserted.
    val insert: Int,
    internal val structure: Boolean = false
) : Step() {

    @Suppress("ReturnCount")
    override fun apply(doc: Node): StepResult {
        if (this.structure && (doc.contentBetween( this.from, this.gapFrom) || doc.contentBetween( this.gapTo, this.to))
        ) {
            return StepResult.fail("Structure gap-replace would overwrite content")
        }

        val gap = doc.slice(this.gapFrom, this.gapTo)
        if (gap.openStart != 0 || gap.openEnd != 0) {
            return StepResult.Companion.fail("Gap is not a flat range")
        }
        val inserted = this.slice.insertAt(this.insert, gap.content)
            ?: return StepResult.Companion.fail("Content does not fit in gap")
        return StepResult.Companion.fromReplace(doc, this.from, this.to, inserted)
    }

    override fun getMap() = StepMap(
        listOf(
            this.from,
            this.gapFrom - this.from,
            this.insert,
            this.gapTo,
            this.to - this.gapTo,
            this.slice.size - this.insert
        )
    )

    override fun invert(doc: Node): ReplaceAroundStep {
        val gap = this.gapTo - this.gapFrom
        return ReplaceAroundStep(
            from = this.from,
            to = this.from + this.slice.size + gap,
            gapFrom = this.from + this.insert,
            gapTo = this.from + this.insert + gap,
            slice = doc.slice(this.from, this.to).removeBetween(this.gapFrom - this.from, this.gapTo - this.from),
            insert = this.gapFrom - this.from,
            structure = this.structure
        )
    }

    @Suppress("ComplexCondition")
    override fun map(mapping: Mappable): ReplaceAroundStep? {
        val from = mapping.mapResult(this.from, 1)
        val to = mapping.mapResult(this.to, -1)
        val gapFrom = if (this.from == this.gapFrom) from.pos else mapping.map(this.gapFrom, -1)
        val gapTo = if (this.to == this.gapTo) to.pos else mapping.map(this.gapTo, 1)
        if ((from.deletedAcross && to.deletedAcross) || gapFrom < from.pos || gapTo > to.pos) return null
        return ReplaceAroundStep(from.pos, to.pos, gapFrom, gapTo, this.slice, this.insert, this.structure)
    }

    override fun toJSON() = buildJsonObject {
        put("stepType", "replaceAround")
        put("from", from)
        put("to", to)
        put("gapFrom", gapFrom)
        put("gapTo", gapTo)
        put("insert", insert)
        if (slice.size != 0) slice.toJSON()?.let { put("slice", it) }
        if (structure) put("structure", true)
    }

    companion object : StepJsonParser<ReplaceAroundStep> {
        init {
            jsonID("replaceAround", this)
        }

        override fun fromJSON(schema: Schema, json: JsonObject): ReplaceAroundStep {
            val from = json["from"]?.jsonPrimitive?.int ?: 0
            val to = json["to"]?.jsonPrimitive?.int ?: 0
            val gapFrom = json["gapFrom"]?.jsonPrimitive?.int ?: 0
            val gapTo = json["gapTo"]?.jsonPrimitive?.int ?: 0
            val slice = json["slice"]?.jsonObject
            val insert = json["insert"]?.jsonPrimitive?.int ?: 0
            val structure = json["structure"]?.jsonPrimitive?.booleanOrNull ?: false
            return ReplaceAroundStep(
                from,
                to,
                gapFrom,
                gapTo,
                Slice.Companion.fromJSON(schema, slice),
                insert,
                structure
            )
        }
    }
}

fun Step.Companion.replaceAround(
    // The start position of the replaced range.
     from: Int,
    // The end position of the replaced range.
     to: Int,
    // The start of preserved range.
     gapFrom: Int,
    // The end of preserved range.
     gapTo: Int,
    // The slice to insert.
     slice: Slice,
    // The position in the slice where the preserved range should be inserted.
     insert: Int
) = ReplaceAroundStep(from,to,gapFrom,gapTo,slice,insert)